--- 模块功能：数据链路激活、SOCKET管理(创建、连接、数据收发、状态维护)
-- @module socket
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2020.03.03
require "link"
require "utils"
module(..., package.seeall)
local req = ril.request
local ver = rtos.get_version()
local ssl = ver:find("SSL") or ver:find("8955F") or ver:find("1802")
-- socketID
local valid = {"0", "1", "2", "3", "4", "5", "6", "7"}
-- socket对象集合
local sockets = {}
-- 单次发送数据最大值
local SENDSIZE = 1460
--- SOCKET 是否有可用
-- @return 可用true,不可用false
socket.isReady = link.isReady
--订阅rsp返回的消息处理函数
local function onSocketURC(data, prefix)
    local tag, id, result = string.match(data, "([SSL]*)[&]*(%d), *([%u :%d]+)")
    -- log.warn("socket.onSocketURC:", tag, id, result)
    if not id or not sockets[id] then
        log.warn('socket: urc on nil socket', data, id, sockets[id])
        return
    end
    if result == "CONNECT OK" or result:match("CONNECT ERROR") or result:match("CONNECT FAIL") then
        sockets[id].connected, sockets[id].wait = result == "CONNECT OK", "connect"
        if sockets[id].connected and sockets[id].callbacks.open then sockets[id].callbacks.open() end
        if sockets[id].co then
            coroutine.resume(sockets[id].co, result == "CONNECT OK")
        end
        return
    end
    --服务器主动关闭连接
    if result:find("CLOSED") or result:find("ERROR") then
        if sockets[id].ssl then req("AT+SSLDESTROY=" .. id) end
        sockets[id].connected, sockets[id].wait = false, "error"
        if sockets[id].callbacks.error then
            sockets[id].callbacks.error(result)
        end
        if sockets[id].co then
            coroutine.resume(sockets[id].co, false)
        end
        return
    end
end
-- Socket对象的封装
local mt = {}
mt.__index = mt
--- 创建socket对象
-- @string host: 服务器地址,支持域名和IP地址
-- @number port: 服务器端口,支持数字或数字字符串
-- @string prot,可选值"tcp","udp","ssl"
-- @table[opt=nil] cert，ssl连接需要的证书配置，只有protocol参数为"tcp"时，才参数才有意义，cert格式如下：
--  {
--     caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式)，如果存在此参数，则表示客户端会对服务器的证书进行校验；不存在则不校验
--     clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式)，服务器对客户端的证书进行校验时会用到此参数
--     clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
--     clientPassword = "123456", --客户端证书文件密码[可选]
--  }
-- @return client，创建成功返回socket客户端对象；创建失败返回nil
-- @usage
-- c=socket.new("www.163.com",80,"udp")
-- c=socket.new("www.163.com",80,"tcp")
-- c=socket.new("www.163.com",80,"tcp",{caCert="ca.crt"})
-- c=socket.new("www.163.com",80,"tcp",{caCert="ca.crt",clientCert="client.crt",clientKey="client.key"})
-- c=socket.new("www.163.com",80,"tcp",{caCert="ca.crt",clientCert="client.crt",clientKey="client.key",clientPassword="123456"})
function new(host, port, prot, cert)
    local tcpssl = prot and prot:upper():find("SSL")
    prot = prot and prot:upper():sub(1, 3) or "TCP"
    if tcpssl and not ssl then
        log.error("socket.new:", "不支持SSL的底层:", rtos.get_version())
        return false
    end
    local function readCert(f)
        if not f then return end
        f = f:sub(1, 1) == "/" and f or "/ldata/" .. f
        if io.exists(f) then return io.readFile(f) end
    end
    return setmetatable({
        id = nil,
        host = host,
        port = port,
        timeout = nil,
        retrymin = nil,
        retrymax = nil,
        prot = prot,
        ssl = tcpssl,
        cert = cert,
        caCert = cert and readCert(cert.caCert),
        clientCert = cert and readCert(cert.clientCert),
        clientKey = cert and readCert(cert.clientKey),
        clientPassword = cert and cert.clientPassword or "",
        co = nil, -- 主线程
        sco = nil, -- send 线程
        input = "",
        output = {},
        putbuff = {},
        callbacks = {},
        wait = "idle",
        sendwait = "idle",
        dns = nil,
        dnsidx = 1,
        connected = false,
        overflow = false,
        maxValue = 32768, -- 缓冲区最大值
        flen = {["*l"] = 1, ["*L"] = 2, ["*s"] = 1, ["*n"] = 1, ["*p"] = 1},
        ft = {["*l"] = "\n", ["*L"] = "\r\n", ["*s"] = "%s", ["*n"] = "%d", ["*p"] = "%p"}
    }, mt)
end
--- 创建基于TCP的socket对象(旧接口)
-- @bool[opt=nil] ssl，是否为ssl连接，true表示是，其余表示否
-- @table[opt=nil] cert，ssl连接需要的证书配置，只有ssl参数为true时，才参数才有意义，cert格式如下：
-- {
--     caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式)，如果存在此参数，则表示客户端会对服务器的证书进行校验；不存在则不校验
--     clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式)，服务器对客户端的证书进行校验时会用到此参数
--     clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
--     clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @return client，创建成功返回socket客户端对象；创建失败返回nil
-- @usage
-- c = socket.tcp()
-- c = socket.tcp(true)
-- c = socket.tcp(true, {caCert="ca.crt"})
-- c = socket.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key"})
-- c = socket.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key", clientPassword="123456"})
function tcp(ssl, cert)
    return new(nil, nil, ssl == true and "SSL" or "TCP", cert)
end
--- 创建基于UDP的socket对象(旧接口)
-- @return client，创建成功返回socket客户端对象；创建失败返回nil
-- @usage c = socket.udp()
function udp()
    return new(nil, nil, "udp")
end
--- socket:on 注册函数
-- @string event,事件，可选值"recv","sent","open","close","error"
-- @function callback,回调方法,形参见例子
-- @usage mt:on("recv",function(msg)print(msg)end)
function mt:on(event, callback)
    self.callbacks[event] = callback
end
--- 获取当前缓冲区的数据长度
-- @return number
-- @usage c:available()
function mt:available()
    return #self.input
end
--- 获取socket的当前状态
-- @return string："idle","recv","connect","close","error"
-- @usage c:status()
function mt:status()
    return self.wait
end
--- 获取socket与服务器连接是否在在线
-- @return boolean: true 连接成功,false,连接失败
-- @usage c:online()
function mt:online()
    return self.connected
end
--- 获取通道接收缓冲区大小
-- @return number
-- @usage c:getRecvBuf()
function mt:getRecvBuf()
    return self.maxValue
end
--- 设置通道接收缓冲区大小
-- @number v：不小于8192的值
-- @usage c:setRecvBuf()
function mt:setRecvBuf(v)
    v = tonumber(v) or self.maxValue
    self.maxValue = v > self.maxValue and v or self.maxValue
end
--- 获取一次能发送数据的最大长度
-- @return number
-- @usage c:getSendSize()
function mt:getSendSize()
    return SENDSIZE
end
--- 注册截止符
-- @string word : recv(timeout,format, frameTimeout)的format 可选参数
-- @string val : format 对应的正则表达式
-- @string len : 正则表达式匹配关键字的最大长度
-- @return nil
-- @usage mt:regFormat("*S"," ",1)
-- @usage mt:regFormat("*xyz","xyz",3)
-- @usage mt:regFormat("*t","[%s%p]",1)
function mt:regFormat(cutWord, val, len)
    if not cutWord or not val then return end
    len = tonumber(len) and tonumber(len) or 1
    self.ft[cutWord], self.flen[cutWord] = val, len
end
--- 连接服务器
-- @string[opt=nil]host: new()方法时可为nil,服务器地址支持ip和域名
-- @number[opt=nil]port: new()方法时可为nil,服务器端口
-- @number [opt=nil]timeout, 链接服务器最长超时时间
-- @param [opt=nil]userdns,用户自定义DNS服务器,为string时为单DNS服务器
-- @param [opt=nil]userdns,用户自定义DNS服务器,为table时为多个DNS服务器
-- @param [opt=nil]userdns,用户自定义DNS服务器,为true时为使用DNS库的服务器
-- @return bool result true - 成功，false - 失败
-- @usage c = socket.new(); c:connect()
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120)
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120,true)
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120,"114.114.114.114")
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120,{"114.114.114.114","114.114.115.115"})
function mt:connect(host, port, timeout, userdns)
    self.co, self.timeout = coroutine.running(), timeout
    self.host, self.port = host or self.host, port or self.port
    if not link.isReady() then
        log.warn("socket.connect: ip not ready!")
        return false
    end
    if cc and cc.anyCallExist() then
        log.warn("socket:connect: call exist, cannot connect!")
        return false
    end
    self.id = self.id or table.remove(valid)
    if not self.id then
        log.warn("socket.socket: too many sockets")
        return false
    end
    sockets[self.id] = self
    -- 如果链接不成功,则尝试修改用户定义的DNS服务器
    if userdns and self.wait == "connecting" then
        local function setDNS(primary, secondary)
            log.info("socket DNS:", primary, secondary)
            ril.request('AT+CDNSCFG="' .. primary .. '","' .. (secondary or "") .. '"')
            ril.request("AT+CDNSCFG?")
        end
        if type(userdns) == "table" then
            self.dns = userdns
        elseif type(userdns) == "string" then
            self.dns = {userdns}
        else
            self.dns = {
                "114.114.114.114", -- 114 DNS
                "114.114.115.115",
                "223.5.5.5", --阿里 DNS
                "223.6.6.6",
                "119.29.29.29", -- 腾讯 DNS
                "182.254.116.116",
                "1.2.4.8", -- 中国互联网中心 DNS
                "180.76.76.76", -- 百度 DNS
                "208.67.220.220", -- Open DNS
                "8.8.8.8", -- 谷歌 DNS
            }
        end
        setDNS(self.dns[self.dnsidx], self.dns[self.dnsidx + 1])
        self.dnsidx = 2 + self.dnsidx
        if self.dnsidx > #self.dns then
            self.dnsidx = 1
            log.error("socket:connect:", "无法解析域名！")
        end
    end
    if self.ssl then
        req("AT+SSLINIT")
        req(string.format('AT+SSLCREATE=%d,"%s",%d', self.id, self.host .. ":" .. self.port, self.cert.caCert and 0 or 1))
        if #self.caCert then
            req(string.format('AT+SSLCERT=0,"cacrt","%s",%d,%d', self.cert.caCert, 1, #self.caCert), self.caCert)
            req(string.format('AT+SSLCERT=1,%d,"cacrt","%s"', self.id, self.cert.caCert))
        end
        if self.clientCert then
            req(string.format('AT+SSLCERT=0,"localcrt","%s",%d,%d', self.cert.clientCert, 1, #self.clientCert), self.clientCert)
            req(string.format('AT+SSLCERT=1,%d,"localcrt","%s","%s"', self.id, self.cert.clientCert, self.clientPassword))
        end
        if self.clientKey then
            req(string.format('AT+SSLCERT=0,"localprivatekey","%s",%d,%d', self.cert.clientKey, 1, #self.clientKey), self.clientKey)
            req(string.format('AT+SSLCERT=1,%d,"localprivatekey","%s"', self.id, self.cert.clientKey))
        end
        req("AT+SSLCONNECT=" .. self.id)
    else
        req(string.format('AT+CIPSTART=%d,"%s","%s",%s', self.id, self.prot, self.host, self.port))
    end
    self.wait, self.connected = "connecting", false
    ril.regUrc((self.ssl and "SSL&" or "") .. self.id, onSocketURC)
    if self.co then return sys.wait((timeout or 120) * 1000) or false end
    return true
end
--- 读取socket缓冲区的数据(非阻塞)
-- @number [opt=nil]length，number类型，不填则读取整个缓冲区
-- @return string,读取指定长度的数据,无数据返回""
-- @usage mt:read(1460)
function mt:read(length)
    local len = tonumber(length)
    local str = len and self.input:sub(1, len) or self.input
    self.input = len and self.input:sub(length + 1, -1) or ""
    if self.overflow then req("AT+CIPRXGET=4," .. self.id) end
    return str
end
--- 读取socket数据,支持字节流和帧模式,线程中使用
-- @number[opt=nil] timeout: 等待缓冲区非空的超时,nil为一直阻塞
-- @string[opt=nil] message：退出recv阻塞的消息,nil 为不支持消息退出
-- 帧模式支持定界，定长，定时进行数据分帧
-- 定界默认支持自定义截止符,默认截止符如下：
-- @param[opt=nil] format: 帧模式的截止符
--      *l：读取到结束字符\n或超时
--      *s：读取到空格字符或者超时
--      数字，读取指定长度的数据或超时
--      nil, 超时后读取缓冲区的全部数据
-- @number[opt=nil]  frameTimeout: 与 format 搭配的超时设置
-- format和frameTimeout 组合模式如下：
--      format=nil, waitTime=nil : 为默认的字节流模式
--      format=nil, frameTimeout=number,为帧模式的定时模式(超时返回所有数据)
--      format="*l", frameTimeout=nil,定界模式,读取到\n返回数据,否则阻塞
--      format=number, frameTimeout=nil,定长模式,读取指定长度数据,否则阻塞
--      format="*l", frameTimeout=number,定界模式,读取到\n返回数据,否则超时后返回false
--      format=number, frameTimeout=number,定长模式,读取指定长度数据,否则超时后返回false
-- @return result,data,param: 有三个返回值，组合如下：
-- @return true,data,nil ：返回接收到的数据；
-- @return false,"timeout",nil : 缓冲区非空超时
-- @return false message,param ：收到message消息,param为消息携带的数据
-- @return false,nil,nil : socket 发生错误
function mt:recv(timeout, message, format, frameTimeout)
    local line, idx, state, option = nil, 1, "recv", false
    self.co, self.wait = coroutine.running(), "recv"
    if message and not self.msg then
        self.msg = message
        self.subtopic = function(str)
            table.insert(self.output, str)
            if self.wait == "recv" then
                coroutine.resume(self.co, "self.msg")
            end
        end
        sys.subscribe(self.msg, self.subtopic)
    end
    -- 处理历史消息中断的数据
    if #self.output > 0 then
        return false, self.msg, table.remove(self.output, 1)
    end
    repeat
        if type(format) == "number" then
            line = #self.input >= format and self.input:sub(1, format)
        elseif self.ft[format] then
            local _, endidx = self.input:find(".-" .. self.ft[format], idx)
            if endidx then
                line = self.input:sub(1, endidx)
            else
                idx = #self.input > self.flen[format] and #self.input - self.flen[format] or 1
            end
        elseif type(frameTimeout) == "number" then
            option = true
        else
            line = #self.input > 0 and self.input
        end
        if line then
            break
        else
            state = sys.wait(#self.input > 0 and frameTimeout or timeout)
            if option and state == nil then line = self.input end
        end
    until state ~= "recv"
    self.wait = "idle"
    if line then
        self.input = self.input:sub(#line + 1, -1)
        if self.overflow then req("AT+CIPRXGET=4," .. self.id) end
        if self.callbacks.recv then self.callbacks.recv(line) end
        return true, line
    elseif state == "self.msg" then
        return false, self.msg, table.remove(self.output, 1)
    elseif state == nil then
        return false, "timeout"
    else
        return false, "offline"
    end
end
--- 发送数据(非阻塞) 最大值1460字节
-- @string str: 要发送给服务器的字符串
-- @return boole:true 为成功,其他为失败
function mt:write(str)
    if not self.connected then
        log.warn("socket:write, connected:", self.connected)
        return false
    end
    if str then
        local res = xpcall(function()
            self.putbuff = table.merge(self.putbuff, str:split(SENDSIZE))
        end, function()print(debug.traceback()) end)
        if not res then
            log.warn("socket:write, wrong data type:", type(str))
            return false
        end
    end
    if self.sendwait == "idle" and #self.putbuff > 0 then
        self.sendwait, str = "sent", table.remove(self.putbuff, 1)
        req(string.format("AT+" .. (self.ssl and "SSL" or "CIP") .. "SEND=%d,%d", self.id, #str), str)
    end
    return true
end
--- 发送数据
-- @string str: 要发送给服务器的字符串
-- @number[opt=120] timeout 可选参数，发送超时时间，单位秒
-- @return result true - 成功，false - 失败
-- @usage  c = socket.tcp(); c:connect(); c:send("12345678");
function mt:send(str, timeout)
    local res = self:write(str)
    self.sco = coroutine.running()
    if res and self.sco then
        return sys.wait((tonumber(timeout) or 120) * 1000) or false
    end
    return res
end
--- 异步收发选择器
-- @number keepAlive,服务器和客户端最大通信间隔时间,也叫心跳包最大时间,单位秒
-- @string pingreq,心跳包的字符串
-- @return boole,false 失败，true 表示成功
function mt:asyncSelect(keepAlive, pingreq)
    assert(self.co == coroutine.running(), "socket:asyncSelect: coroutine mismatch")
    if keepAlive and keepAlive ~= 0 then
        if type(pingreq) == "function" then
            sys.timerStart(pingreq, keepAlive * 1000)
        else
            sys.timerStart(self.asyncSend, keepAlive * 1000, self, pingreq or "\0")
        end
    end
    return coroutine.yield()
end
--- 异步发送数据
-- @string data 数据
-- @number[opt=120] timeout 可选参数，发送超时时间，单位秒
-- @return result true - 成功，false - 失败
-- @usage  c = socket.tcp(); c:connect(); c:asyncSend("12345678");
function mt:asyncSend(data, timeout)
    return self:send(data, timeout)
end
--- 异步接收数据
-- @number len: 要读取的数据长度,默认读取所有缓冲区的数据
-- @return data: 返回指定长度的字符串
-- @usage c = socket.tcp(); c:connect()
-- @usage data = c:asyncRecv()
function mt:asyncRecv(len)
    return self:read(len)
end
-- 销毁当前socket
-- @retrun nil
-- @usage mt:destroy()
function mt:destroy()
    if self.subtopic then
        sys.unsubscribe(self.msg, self.subtopic)
        self.msg = nil
    end
    ril.deRegUrc((self.ssl and "SSL&" or "") .. self.id, onSocketURC)
    table.insert(valid, 1, self.id)
    self.sendwait = "idle"
    sockets[self.id], self.id = nil, nil
end
--- 断开当前连接
-- @return nil
-- @usage  c = socket.tcp(); c:connect(); c:send("123"); c:close()
function mt:close(slow)
    self.wait = "closing"
    if self.id then
        if self.connected then
            req(self.ssl and ("AT+SSLDESTROY=" .. self.id) or ("AT+CIPCLOSE=" .. self.id .. (slow and ",0" or "")))
            self.connected = false
            if self.co and self.co == coroutine.running() then coroutine.yield() end
        end
        self:destroy()
        if self.callbacks.close then self.callbacks.close() end
    end
end
--- 设置TCP层自动重传的参数
-- @number[opt=4] retryCnt，重传次数；取值范围0到12
-- @number[opt=16] retryMaxTimeout，限制每次重传允许的最大超时时间(单位秒)，取值范围1到16
-- @return nil
-- @usage
-- setTcpResendPara(3,8)
-- setTcpResendPara(4,16)
function setTcpResendPara(retryCnt, retryMaxTimeout)
    req("AT+TCPUSERPARAM=6," .. (retryCnt or 4) .. ",7200," .. (retryMaxTimeout or 16))
    ril.setDataTimeout(((retryCnt or 4) * (retryMaxTimeout or 16) + 60) * 1000)
end
--- 设置数据发送模式（在网络准备就绪之前调用此接口设置）.
-- 如果设置为快发模式，注意如下两点：
-- 1、通过send接口发送的数据，如果成功发送到服务器，设备端无法获取到这个成功状态
-- 2、通过send接口发送的数据，如果发送失败，设备端可以获取到这个失败状态
-- 慢发模式可以获取到send接口发送的成功或者失败
--
-- ****************************************************************************************************************************************************************
-- TCP协议发送数据时，数据发送出去之后，必须等到服务器返回TCP ACK包，才认为数据发送成功，在网络较差的情况下，这种ACK确认就会导致发送过程很慢。
-- 从而导致用户程序后续的AT处理逻辑一直处于等待状态。例如执行AT+CIPSEND动作发送一包数据后，接下来要执行AT+QTTS播放TTS，但是CIPSEND一直等了1分钟才返回SEND OK，
-- 这时AT+QTTS就会一直等待1分钟，可能不是程序中想看到的。
-- 此时就可以设置为快发模式，AT+CIPSEND可以立即返回一个结果，此结果表示“数据是否被缓冲区所保存”，从而不影响后续其他AT指令的及时执行
--
-- AT版本可以通过AT+CIPQSEND指令、Luat版本可以通过socket.setSendMode接口设置发送模式为快发或者慢发
--
-- 快发模式下，在core中有一个1460*7=10220字节的缓冲区，要发送的数据首先存储到此缓冲区，然后在core中自动循环发送。
-- 如果此缓冲区已满，则AT+CIPSEND会直接返回ERROR，socket:send接口也会直接返回失败
--
-- 同时满足如下几种条件，适合使用快发模式：
-- 1.	发送的数据量小，并且发送频率低，数据发送速度远远不会超过core中的10220字节大小；
--      没有精确地判断标准，可以简单的按照3分钟不超过10220字节来判断；曾经有一个不适合快发模式的例子如下：
--      用户使用Luat版本的http上传一个几十K的文件，设置了快发模式，导致一直发送失败，因为循环的向core中的缓冲区插入数据，
--      插入数据的速度远远超过发送数据到服务器的速度，所以很快就导致缓冲区慢，再插入数据时，就直接返回失败
-- 2.	对每次发送的数据，不需要确认发送结果
-- 3.	数据发送功能不能影响其他功能的及时响应
-- ****************************************************************************************************************************************************************
--
-- @number[opt=0] mode，数据发送模式，0表示慢发，1表示快发
-- @return nil
-- @usage socket.setSendMode(1)
function setSendMode(mode)
    link.setSendMode(mode)
end
setTcpResendPara(4, 16)
local function onResponse(cmd, success, response, intermediate)
    -- log.warn("socket.onResponse:", cmd, success, response, intermediate)
    local prefix, id, acceptLen = string.match(cmd, "AT(%+%u+)=(%d),?(%d*)")
    if not sockets[id] then
        log.warn('socket.onResponse nil socket', cmd, response)
        return
    end
    -- cipsend 如果正好pdp deact会返回+PDP: DEACT作为回应
    if response == '+PDP: DEACT' then
        sys.publish('PDP_DEACT_IND')
        sockets[id].connected, sockets[id].wait = false, "error"
        if sockets[id].callbacks.error then
            sockets[id].callbacks.error(result)
        end
        if sockets[id].co then
            coroutine.resume(sockets[id].co, false)
        end
        return
    end
    if prefix == "+CIPCLOSE" or response:find("DESTROY OK") then
        sockets[id].connected, sockets[id].wait = false, "closed"
        if sockets[id].co then
            coroutine.resume(sockets[id].co, false)
        end
        return
    end
    -- CIPSTART,SSLCONNECT 返回OK只是表示被接受
    if (prefix == "+CIPSTART" or prefix == "+SSLCONNECT") then
        if not success then
            if sockets[id].co then
                coroutine.resume(sockets[id].co, false)
            end
        end
        return
    end
    if prefix == "+CIPSEND" or prefix == "+SSLSEND" then
        if response:match("%d, *([%u%d :]+)") ~= 'SEND OK' then
            success = acceptLen == response:match("DATA ACCEPT:%d,(%d+)")
        end
        sockets[id].sendwait = "idle"
        if #sockets[id].putbuff > 0 and success then
            sockets[id].write(sockets[id])
        else
            if sockets[id].callbacks.sent then
                sockets[id].callbacks.sent(success)
            end
            if sockets[id].sco then
                coroutine.resume(sockets[id].sco, success)
            end
        end
    end
end
local function onSocketReceiveUrc(msg)
    local mode, sid, slen, srlen = ""
    if msg:find("+CIPRXGET") then
        mode, sid, slen, srlen = msg:match("+CIPRXGET: (%d),(%d),?(%d*),?(%d*)")
    else
        mode, sid, slen = msg:match("([SSL]* *RECEIVE),(%d), *(%d+)")
    end
    if not sockets[sid] then
        log.warn('socket.onSocketReceiveUrc nil socket', cmd, response)
        return msg
    end
    local cache, len, rlen = {}, tonumber(slen) or 0, tonumber(srlen) or 0
    local function filter(data)
        if #data < len then
            table.insert(cache, data)
            len = len - #data
            return "", filter
        else
            table.insert(cache, data:sub(1, len))
            sockets[sid].input = sockets[sid].input .. table.concat(cache)
            sys.publish("SOCKET_RECV", sid)-- 兼容之前代码
            if sockets[sid].co and sockets[sid].wait == "recv" then
                coroutine.resume(sockets[sid].co, "recv")
            end
            return data:sub(len + 1, -1)
        end
    end
    if mode == "1" then
        req("AT+CIPRXGET=4," .. sid)
    elseif mode == "2" then
        if rlen > 0 then req("AT+CIPRXGET=2," .. sid .. ",1460") end
        return filter
    elseif mode == "4" and len > 0 then
        if #sockets[sid].input < sockets[sid].maxValue then
            sockets[sid].overflow = false
            return req("AT+CIPRXGET=2," .. sid .. ",1460")
        end
        sockets[sid].overflow = true
    elseif mode:find("RECEIVE") then
        return filter
    end
end
ril.regRsp("+CIPCLOSE", onResponse)
ril.regRsp("+CIPSEND", onResponse)
ril.regRsp("+CIPSTART", onResponse)
ril.regRsp("+SSLDESTROY", onResponse)
ril.regRsp("+SSLSEND", onResponse)
ril.regRsp("+SSLCONNECT", onResponse)
ril.regRsp("+CIPRXGET", function(cmd, success, response)
    log.info(cmd, success, response)
end)
ril.regUrc("+CIPRXGET", onSocketReceiveUrc)
ril.regUrc("+RECEIVE", onSocketReceiveUrc)
ril.regUrc("+SSL RECEIVE", onSocketReceiveUrc)
-- 移动场景关闭时候的处理：
sys.subscribe("IP_ERROR_IND", function()
    for k, v in pairs(sockets) do
        if v.callbacks.error then v.callbacks.error(v, false) end
        if v.co and coroutine.status(v.co) == "suspended" then
            coroutine.resume(v.co, false)
        end
    end
end)
--- 需要在任务中启动,带自动重连,支持心跳协议
-- @number[opt=nil] keepAlive：网络连接保活时间(心跳包),单位秒
-- @string[opt=nil] message: 同recv()的 message 参数
-- @param[opt=nil] format: 同recv()的 format 参数
-- @parma[opt=nil] frameTimeout: 同recv()的 frameTimeout 参数
-- @string[opt=nil] heart：心跳包字符串
-- @function[opt=nil] proc 处理服务器下发消息的函数
-- @return 无
-- @usage sys.taskInit(mt.start,mt,180,"heart",nil,25)
-- @usage sys.taskInit(mt.start,mt,180,"heart",nil,25,nil,function(msg)u1:send(msg) end)
function mt:start(keepAlive, message, format, frameTimeout, heart, proc)
    keepAlive = tonumber(keepAlive) and keepAlive * 1000 or nil
    while true do
        while not socket.isReady() do sys.wait(1000) end
        while not self:connect() do sys.wait(2000) end
        while true do
            local r, s, p = self:recv(keepAlive, message, format, frameTimeout)
            if r then
                if proc then proc(s) end
            elseif message and s == message then
                log.info("send message:", p)
                if not self:send(p) then break end
            elseif s == "timeout" and heart then
                if not self:send(tostring(heart)) then break end
            else
                log.error('socket receive error', r, s)
                break
            end
        end
        self:close()
    end
end
